﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunctionGraph
{
    static class OperatorLibrary
    {
        private static Interval Neg(Interval v1)
        {
            Interval rs;
            rs.Val0 = -v1.Val1;
            rs.Val1 = -v1.Val0;
            rs.Cont0 = v1.Cont0;
            rs.Cont1 = v1.Cont1;
            rs.Def0 = v1.Def0;
            rs.Def1 = v1.Def1;
            return rs;
        }
        private static Interval Sub(Interval v1, Interval v2)
        {
            Interval rs;
            rs.Val0 = v1.Val0 - v2.Val1;
            rs.Val1 = v1.Val1 - v2.Val0;
            rs.Cont0 = v1.Cont0 && v2.Cont0;
            rs.Cont1 = true;
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            return rs;
        }
        private static Interval Add(Interval v1, Interval v2)
        {
            Interval rs;
            rs.Val0 = v1.Val0 + v2.Val0;
            rs.Val1 = v1.Val1 + v2.Val1;
            rs.Cont0 = v1.Cont0 && v2.Cont0;
            rs.Cont1 = true;
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            return rs;
        }
        private static Interval Mul(Interval v1, Interval v2)
        {
            Interval rs;
            double r0 = v1.Val0 * v2.Val0;
            double r1 = v1.Val1 * v2.Val0;
            double r2 = v1.Val0 * v2.Val1;
            double r3 = v1.Val1 * v2.Val1;
            rs.Val0 = Math.Min(Math.Min(r0, r1), Math.Min(r2, r3));
            rs.Val1 = Math.Max(Math.Max(r0, r1), Math.Max(r2, r3));
            rs.Cont0 = v1.Cont0 && v2.Cont0;
            rs.Cont1 = true;
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            return rs;
        }
        private static IntervalSet Div(Interval v1, Interval v2)
        {
            if (TestInput(v1, v2))
                return new IntervalSet();
            if (v2.Val0 < 0 && v2.Val1 > 0)
            {
                IntervalSet rs = new IntervalSet();
                rs.Intervals.Add(new Interval() { Val0 = double.NegativeInfinity, Val1 = 1.0 / v2.Val0, Def1 = true, Def0 = false, Cont1 = true, Cont0 = true });
                rs.Intervals.Add(new Interval() { Val0 = 1.0 / v2.Val1, Val1 = double.PositiveInfinity, Def1 = true, Def0 = false, Cont1 = true, Cont0 = true });
                return Mul(new IntervalSet(v1), rs);
            }
            else if (v2.Val0 == 0)
            {
                return new IntervalSet(Mul(v1, new Interval() { Val0 = 1.0 / v2.Val1, Val1 = double.PositiveInfinity, Def1 = true, Def0 = false, Cont1 = true, Cont0 = true }));
            }
            else if (v2.Val1 == 0)
            {
                return new IntervalSet(Mul(v1, new Interval() { Val0 = double.NegativeInfinity, Val1 = 1.0 / v2.Val0, Def1 = true, Def0 = false, Cont1 = true, Cont0 = true }));
            }
            else
                return new IntervalSet(IntervalDiv.div_non_zero(v1, v2));
        }

        private static Interval Abs(Interval v)
        {
            Interval rs;
            rs.Def0 = v.Def0;
            rs.Def1 = v.Def1;
            bool cont = true;
            if (zero_in(v))
            {
                cont = false;
                rs.Val0 = 0;
                rs.Val1 = Math.Max(Math.Abs(v.Val0), Math.Abs(v.Val1));
            }
            else
            {
                double r0 = Math.Abs(v.Val0);
                double r1 = Math.Abs(v.Val1);
                if (r0 < r1)
                {
                    rs.Val0 = r0;
                    rs.Val1 = r1;
                }
                else
                {
                    rs.Val0 = r1;
                    rs.Val1 = r0;
                }
            }
            rs.Cont0 = v.Cont0 && cont;
            rs.Cont1 = v.Cont1 && cont;
            return rs;
        }

        private static IntervalSet Floor(Interval v)
        {
            var r0 = (double)Math.Floor(v.Val0);
            var r1 = (double)Math.Floor(v.Val1);
            if (r0 == r1)
            {
                return new IntervalSet(new Interval(r0, r1) { Def0 = v.Def0, Def1 = v.Def1, Cont0 = v.Def0, Cont1 = v.Def1 });
            }
            else if (r1 - r0 == 1.0)
            {
                IntervalSet rs = new IntervalSet();
                rs.Intervals.Add(new Interval(r0, r0) { Def0 = false, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
                rs.Intervals.Add(new Interval(r1, r1) { Def0 = false, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
                return rs;
            }
            else
                return new IntervalSet(new Interval(r0, r1) { Def0 = v.Def0, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
        }

        private static IntervalSet Ceil(Interval v)
        {
            var r0 = (double)Math.Ceiling(v.Val0);
            var r1 = (double)Math.Ceiling(v.Val1);
            if (r0 == r1)
            {
                return new IntervalSet(new Interval(r0, r1) { Def0 = v.Def0, Def1 = v.Def1, Cont0 = v.Def0, Cont1 = v.Def1 });
            }
            else if (r1 - r0 == 1.0)
            {
                IntervalSet rs = new IntervalSet();
                rs.Intervals.Add(new Interval(r0, r0) { Def0 = false, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
                rs.Intervals.Add(new Interval(r1, r1) { Def0 = false, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
                return rs;
            }
            else
                return new IntervalSet(new Interval(r0, r1) { Def0 = v.Def0, Def1 = v.Def1, Cont0 = false, Cont1 = v.Def1 });
        }

        private static bool zero_in(Interval y)
        {
            return y.Val0 <= 0 && y.Val1 >= 0;
        }

        private static bool TestInput(Interval x, Interval y)
        {
            return TestInput(x) || TestInput(y);
        }

        private static bool TestInput(Interval x)
        {
            return x.Val0 > x.Val1 || (x.Def0 == false && x.Def1 == false);
        }



        private static IntervalSet Pow(Interval v1, Interval v2)
        {
            if (v2.Val0 >= 1.0)
            {
                return new IntervalSet(Pow_GreaterOne(v1, v2));
            }
            else if (v2.Val0 >= 0)
            {
                double l = v1.Val0;
                double h = v1.Val1;
                if (l >= 0.0)
                    return new IntervalSet(Pow_GreaterOne(new Interval(l, h), v2));
                else if (h >= 0.0)
                    return new IntervalSet(Pow_GreaterOne(new Interval(0.0, h), v2));
                else
                    return new IntervalSet();
            }
            else 
            {
                if (zero_in(v2))
                {
                    var part1 = Div(new IntervalSet(new Interval(1.0, 1.0)), 
                        Pow(v1, new Interval(0.0, -v2.Val0) { Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1 }));
                    var part2 = Pow(v1, new Interval(0.0, v2.Val1) { Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1 });
                    part1.Intervals.AddRange(part2.Intervals);
                    return part1.Merge();
                }
                else
                {
                    return Div(new IntervalSet(new Interval(1.0, 1.0)), Pow(v1, new Interval(-v2.Val1, -v2.Val0){ Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1}));
                }
            }
           
        }

        private static IntervalSet Pow1(Interval v1, Interval v2)
        {
            if (v2.Val0 >= 1.0)
            {
                return new IntervalSet(Pow_GreaterOne1(v1, v2));
            }
            else if (v2.Val0 >= 0)
            {
                double l = v1.Val0;
                double h = v1.Val1;
                if (l >= 0.0)
                    return new IntervalSet(Pow_GreaterOne1(new Interval(l, h), v2));
                else if (h >= 0.0)
                {
                    var i1 = Pow_GreaterOne1(new Interval(h, h), v2);
                    var i2 = Pow_GreaterOne1(new Interval(-l, -l), v2);
                    return new IntervalSet(new Interval(-i2.Val1, i1.Val1), v2);
                }
                else
                {
                    var i = Pow_GreaterOne1(new Interval(-h, -l), v2);
                    var tmp = -i.Val0;
                    i.Val0 = -i.Val1;
                    i.Val1 = tmp;
                    return new IntervalSet(i);
                }
            }
            else
            {
                if (zero_in(v2))
                {
                    var part1 = Div(new IntervalSet(new Interval(1.0, 1.0)),
                        Pow1(v1, new Interval(0.0, -v2.Val0) { Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1 }));
                    var part2 = Pow1(v1, new Interval(0.0, v2.Val1) { Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1 });
                    part1.Intervals.AddRange(part2.Intervals);
                    return part1.Merge();
                }
                else
                {
                    return Div(new IntervalSet(new Interval(1.0, 1.0)), Pow1(v1, new Interval(-v2.Val1, -v2.Val0) { Def0 = v2.Def0, Def1 = v2.Def1, Cont0 = v2.Cont0, Cont1 = v2.Cont1 }));
                }
            }

        }

        private static Interval Pow_GreaterOne1(Interval v1, Interval v2)
        {
            if (v2.Val0 != v2.Val1)
            {
                if (v1.Val1 < 0)
                {
                    var tmp = v1.Val0;
                    v1.Val0=-v1.Val1;
                    v1.Val1=-tmp;

                    var intv = Pow_GreaterOne(v1, v2);
                    tmp = intv.Val0;
                    intv.Val0 = -intv.Val1;
                    intv.Val1 = -tmp;
                    return intv;
                }
                else if (v1.Val0 < 0)
                {
                    double r0 = -Math.Pow(-v1.Val0, v2.Val0);
                    double r1 = Math.Pow(v1.Val1, v2.Val0);
                    double r2 = -Math.Pow(-v1.Val0, v2.Val1);
                    double r3 = Math.Pow(v1.Val1, v2.Val1);
                    double r4 = r3;
                    if (zero_in(v1))
                        r4 = 0.0f;
                    return new Interval(Math.Min(Math.Min(Math.Min(r0, r1), Math.Min(r2, r3)), r4), Math.Max(Math.Max(Math.Max(r0, r1), Math.Max(r2, r3)), r4)) { Def0 = false, Def1 = true, Cont0 = false, Cont1 = true };
                }
            }
            {
                double r0 = Math.Pow(v1.Val0, v2.Val0);
                double r1 = Math.Pow(v1.Val1, v2.Val0);
                double r2 = Math.Pow(v1.Val0, v2.Val1);
                double r3 = Math.Pow(v1.Val1, v2.Val1);
                double r4 = r3;
                bool def0 = true;
                if (zero_in(v1))
                    if (v2.Val0 > 0)
                        r4 = 0.0f;
                    else
                        def0 = false;
                return new Interval(Math.Min(Math.Min(Math.Min(r0, r1), Math.Min(r2, r3)), r4), Math.Max(Math.Max(Math.Max(r0, r1), Math.Max(r2, r3)), r4)) { Def0 = def0, Def1 = true };
            }
        }

        private static Interval Pow_GreaterOne(Interval v1, Interval v2)
        {
            if (v2.Val0 != v2.Val1 || ((int)v2.Val0 != v2.Val0))
            {
                if (v1.Val1 < 0)
                    return new Interval(double.PositiveInfinity, double.NegativeInfinity) { Def0 = false, Def1 = false };
                else if (v1.Val0 < 0)
                {
                    double r0 = Math.Pow(0.0, v2.Val0);
                    double r1 = Math.Pow(v1.Val1, v2.Val0);
                    double r2 = Math.Pow(0.0, v2.Val1);
                    double r3 = Math.Pow(v1.Val1, v2.Val1);
                    double r4 = r3;
                    if (zero_in(v1))
                        r4 = 0.0f;
                    return new Interval(Math.Min(Math.Min(Math.Min(r0, r1), Math.Min(r2, r3)), r4), Math.Max(Math.Max(Math.Max(r0, r1), Math.Max(r2, r3)), r4)) { Def0 = false, Def1 = true, Cont0 = false, Cont1 = true };
                }
            }
            {
                double r0 = Math.Pow(v1.Val0, v2.Val0);
                double r1 = Math.Pow(v1.Val1, v2.Val0);
                double r2 = Math.Pow(v1.Val0, v2.Val1);
                double r3 = Math.Pow(v1.Val1, v2.Val1);
                double r4 = r3;
                bool def0 = true;
                if (zero_in(v1))
                    if (v2.Val0 > 0)
                        r4 = 0.0f;
                    else
                        def0 = false;
                return new Interval(Math.Min(Math.Min(Math.Min(r0, r1), Math.Min(r2, r3)), r4), Math.Max(Math.Max(Math.Max(r0, r1), Math.Max(r2, r3)), r4)) { Def0 = def0, Def1 = true };
            }
        }

        private static Interval Less(Interval v1, Interval v2)
        {
            Interval rs;
            if (v1.Val1 <= v2.Val0)
            {
                rs.Val0 = 1;
                rs.Val1 = 1;
            }
            else if (v1.Val0 >= v2.Val1)
            {
                rs.Val0 = 0;
                rs.Val1 = 0;
            }
            else
            {
                rs.Val0 = 0;
                rs.Val1 = 1;
            }
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            if (rs.Val0 != rs.Val1)
            {
                rs.Cont0 = false;
                rs.Cont1 = false;
            }
            else
            {
                rs.Cont0 = true;
                rs.Cont1 = true;
            }
            return rs;
        }

        private static Interval Greater(Interval v1, Interval v2)
        {
            Interval rs;
            if (v1.Val0 > v2.Val1)
            {
                rs.Val0 = 1;
                rs.Val1 = 1;
            }
            else if (v1.Val1 < v2.Val0)
            {
                rs.Val0 = 0;
                rs.Val1 = 0;
            }
            else
            {
                rs.Val0 = 0;
                rs.Val1 = 1;
            }
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            if (rs.Val0 != rs.Val1)
            {
                rs.Cont0 = false;
                rs.Cont1 = false;
            }
            else
            {
                rs.Cont0 = true;
                rs.Cont1 = true;
            }
            return rs;
        }

        private static Interval Equal(Interval v1, Interval v2)
        {
            Interval rs;
          
            if (!(v1.Val1<v2.Val0 || v1.Val0>v2.Val1))
            {
                rs.Val0 = 0;
                rs.Val1 = 1;
            }
            else
            {
                rs.Val0 = 0;
                rs.Val1 = 0;
            }
            rs.Def0 = v1.Def0 && v2.Def0;
            rs.Def1 = v1.Def1 && v2.Def1;
            if (rs.Val0 != rs.Val1)
            {
                rs.Cont0 = false;
                rs.Cont1 = false;
            }
            else
            {
                rs.Cont0 = true;
                rs.Cont1 = true;
            }
            return rs;
        }
        static double invPiOver2 = 2.0/Math.PI;
        private static Interval Sin(Interval v1)
        {
            var k1 = (long)Math.Ceiling(v1.Val0 * invPiOver2);
            var k2 = (long)Math.Floor(v1.Val1 * invPiOver2); // may be larger than Int32
            double minV = Math.Sin(v1.Val0);
            double maxV = minV;
            int c = 0;
            for (var i = k1; i <= k2; i++)
            {
                c++;
                if (c > 4)
                    break;
                if (((int)i & 1) == 0)
                    continue;
                
                double v = Math.Sin(i * (Math.PI * 0.5));
                if (minV > v) minV = v;
                if (maxV < v) maxV = v;
            }
            double v2 = Math.Sin(v1.Val1);
            if (minV > v2) minV = v2;
            if (maxV < v2) maxV = v2;
            Interval rs;
            rs.Val0 = minV;
            rs.Val1 = maxV;
            rs.Cont0 = v1.Cont0;
            rs.Cont1 = v1.Cont1;
            rs.Def0 = v1.Def0;
            rs.Def1 = v1.Def1;
            return rs;
        }
        private static Interval Cos(Interval v1)
        {
            var k1 = (long)Math.Ceiling(v1.Val0 * invPiOver2);
            var k2 = (long)Math.Floor(v1.Val1 * invPiOver2);
            double minV = Math.Cos(v1.Val0);
            double maxV = minV;
            int c = 0;
            for (var i = k1; i <= k2; i++)
            {
                c++;
                if (c > 4)
                    break;
                if ((i & 1) == 1)
                    continue;
                double v = Math.Cos(i * (Math.PI * 0.5));
                if (minV > v) minV = v;
                if (maxV < v) maxV = v;
            }
            double v2 = Math.Cos(v1.Val1);
            if (minV > v2) minV = v2;
            if (maxV < v2) maxV = v2;
            Interval rs;
            rs.Val0 = minV;
            rs.Val1 = maxV;
            rs.Cont0 = v1.Cont0;
            rs.Cont1 = v1.Cont1;
            rs.Def0 = v1.Def0;
            rs.Def1 = v1.Def1;
            return rs;
        }
        internal static IntervalSet UnarySetOperation(Func<Interval, Interval> f, IntervalSet v1)
        {
            IntervalSet rs = new IntervalSet();
            foreach (var i1 in v1.Intervals)
                rs.Intervals.Add(f(i1));
            return rs;
        }
        internal static IntervalSet UnarySetOperationS(Func<Interval, IntervalSet> f, IntervalSet v1)
        {
            IntervalSet rs = new IntervalSet();
            foreach (var i1 in v1.Intervals)
                rs.Intervals.AddRange(f(i1).Intervals);
            return rs;
        }

        private static IntervalSet BinarySetOperation(Func<Interval, Interval, Interval> f, IntervalSet v1, IntervalSet v2)
        {
            IntervalSet rs = new IntervalSet();
            foreach(var i1 in v1.Intervals)
                foreach (var i2 in v2.Intervals)
                {
                    rs.Intervals.Add(f(i1, i2));
                }
            return rs;
        }
        private static IntervalSet BinarySetOperationS(Func<Interval, Interval, IntervalSet> f, IntervalSet v1, IntervalSet v2)
        {
            IntervalSet rs = new IntervalSet();
            foreach (var i1 in v1.Intervals)
                foreach (var i2 in v2.Intervals)
                {
                    rs.Intervals.AddRange(f(i1, i2).Intervals);
                }
            return rs;
        }
        public static IntervalSet Add(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Add, v1, v2);
        }
        public static IntervalSet Sub(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Sub, v1, v2);
        }
        public static IntervalSet Mul(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Mul, v1, v2);
        }
        public static IntervalSet Div(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperationS(Div, v1, v2);
        }
        public static IntervalSet Less(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Less, v1, v2);
        }
        public static IntervalSet Greater(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Greater, v1, v2);
        }
        public static IntervalSet Eql(IntervalSet v1, IntervalSet v2)
        {
            return BinarySetOperation(Equal, v1, v2);
        }

        internal static IntervalSet Pow(IntervalSet l, IntervalSet r)
        {
            return BinarySetOperationS(Pow, l, r);
        }

        internal static IntervalSet Neg(IntervalSet l)
        {
            return UnarySetOperation(Neg, l);
        }

        internal static IntervalSet Sin(IntervalSet l)
        {
            return UnarySetOperation(Sin, l);
        }

        internal static IntervalSet Cos(IntervalSet l)
        {
            return UnarySetOperation(Cos, l);
        }

        internal static IntervalSet Pow1(IntervalSet l, IntervalSet r)
        {
            return BinarySetOperationS(Pow1, l, r);
        }
        public static IntervalSet Abs(IntervalSet l)
        {
            return UnarySetOperation(Abs, l);
        }

        internal static IntervalSet Floor(IntervalSet val)
        {
            return UnarySetOperationS(Floor, val);
        }

        internal static IntervalSet Ceil(IntervalSet val)
        {
            return UnarySetOperationS(Ceil, val);
        }
    }
}
